/* Orbas:
 *     A open source CORBA Specification implementation from Huihoo.
 *
 * Copyright 2002-2003 Huihoo.org, Inc. All Right Reserved.
 *
 * This software is licensed under LGPL license.
 * See terms of license at gnu.org.
 *
 * For more information, visit:
 *
 * http://www.huihoo.org/orbas
 */

package org.huihoo.orbas.orb.cdr;

/**
 * <p>Description: </p>
 * CDR encode stream.
 * <p>Copyright (c) 2002,2003</p>
 * <p>Company: <a href="http://www.huihoo.org/">huihoo.org</a></p>
 * @author <a href="http://www.huihoo.org/~mep">mep(mep@huihoo.com)</a>
 * @see <a href="http://www.huihoo.org/orbas">http://www.huihoo.org/orbas</a>
 * @version 1.0
 */

import org.omg.CORBA.TCKind;

public class CDROutputStream extends org.omg.CORBA_2_3.portable.OutputStream {
	private org.huihoo.orbas.orb.ORB orb;
	private byte[] buffer;
	private int nextByteToWrite;

	private java.util.Hashtable valueList;
	private int urlIndex;
	private int level = 0;

	protected final static int VALUE_NO_CODEBASE = 0;

	protected final static int VALUE_CODEBASE = 1;

	protected final static int VALUE_NO_TYPE_INFORMATION = 0;

	protected final static int SINGLE_TYPE_INFORMATION = 2;

	protected final static int MULTIPLE_TYPE_INFORMATION = 6;

	protected final static int CHUNK = 8;

	protected final static int NO_CHUNK = 0;

	protected final static int OPEN_ENCAPS = 0xFFFFFFFF;

	// big endian by default
	public CDROutputStream(org.huihoo.orbas.orb.ORB orb) {
		this.orb = orb;

		buffer = new byte[512];
		nextByteToWrite = 0;

		valueList = new java.util.Hashtable();
		urlIndex = -1;
	}

	public CDROutputStream(byte[] buf, int len) {
		buffer = buf;
		nextByteToWrite = 0;

		valueList = new java.util.Hashtable();
	}

	public org.omg.CORBA.portable.InputStream create_input_stream() {

		// what does this operation means?
		int length = nextByteToWrite + 1;
		byte[] buf = new byte[length];

		System.arraycopy(buffer, 0, buf, 0, length);

		// default big endian assumed
		return new CDRInputStream(orb, buf);
	}

	public void write_encapsulation(CDROutputStream out) {
		int length = out.getLength();

		write_long(length);
		write_octet_array(out.getBuffer(), 0, length);
	}

	public void write_boolean(boolean value) {
		makeRoom(1);
		buffer[nextByteToWrite++] = value ? (byte) 1 : (byte) 0;
	}

	public void write_boolean_array(boolean[] value, int offset, int length) {
		makeRoom(length);

		for (int i = 0; i < length; i++) {
			write_boolean(value[i + offset]);
		}
	}

	public void write_octet(byte value) {
		makeRoom(1);
		buffer[nextByteToWrite++] = value;
	}

	public void write_octet_array(byte[] value, int offset, int length) {
		makeRoom(length);

		for (int i = 0; i < length; i++) {
			write_octet(value[i + offset]);
		}
	}

	public void write_char(char c) {
		makeRoom(1);

		write_octet((byte) (c & 0xFF));
	}

	public void write_char_array(char[] value, int offset, int length) {
		makeRoom(length);

		for (int i = 0; i < length; i++) {
			write_char(value[i + offset]);
		}
	}

	public void write_string(String s) {

		int length = s.length();

		write_long(length + 1);

		makeRoom(length + 1);

		for (int i = 0; i < length; i++) {
			write_char(s.charAt(i));
		}

		write_char((char) 0);
	}

	public void write_short(short value) {
		padding(2);

		makeRoom(2);

		buffer[nextByteToWrite++] = (byte) ((value >>> 8) & 0xFF);
		buffer[nextByteToWrite++] = (byte) (value & 0xFF);
	}

	public void write_short_array(short[] value, int offset, int length) {
		makeRoom(length * 2);

		for (int i = 0; i < length; i++) {
			write_short(value[i + offset]);
		}
	}

	public void write_long(int value) {
		padding(4);

		makeRoom(4);

		buffer[nextByteToWrite++] = (byte) ((value >>> 24) & 0xFF);
		buffer[nextByteToWrite++] = (byte) ((value >>> 16) & 0xFF);
		buffer[nextByteToWrite++] = (byte) ((value >>> 8) & 0xFF);
		buffer[nextByteToWrite++] = (byte) (value & 0xFF);
	}

	public void write_long_array(int[] value, int offset, int length) {
		makeRoom(length * 4);

		for (int i = 0; i < length; i++) {
			write_long(value[i + offset]);
		}
	}

	public void write_wchar(char c) {
		write_short((short) c);
	}

	public void write_wchar_array(char[] value, int offset, int length) {

		makeRoom(length * 2);

		for (int i = 0; i < length; i++) {
			write_wchar(value[i + offset]);
		}
	}

	public void write_wstring(String s) {

		int length = s.length();

		makeRoom((length + 1) * 2);

		write_long(length + 1);

		for (int i = 0; i < length; i++) {
			write_wchar(s.charAt(i));
		}

		write_wchar((char) 0);
	}

	public void write_float(float value) {

		write_long(Float.floatToIntBits(value));
	}

	public void write_float_array(float[] value, int offset, int length) {

		makeRoom(length * 4);

		for (int i = 0; i < length; i++) {
			write_float(value[i + offset]);
		}
	}

	public void write_double(double value) {

		write_longlong(Double.doubleToLongBits(value));
	}

	public void write_double_array(double[] value, int offset, int length) {
		makeRoom(length * 8);

		for (int i = 0; i < length; i++) {
			write_double(value[i + offset]);
		}
	}

	public void write_longlong(long value) {
		padding(8);

		makeRoom(8);

		buffer[nextByteToWrite++] = (byte) ((value >>> 56) & 0xFF);
		buffer[nextByteToWrite++] = (byte) ((value >>> 48) & 0xFF);
		buffer[nextByteToWrite++] = (byte) ((value >>> 40) & 0xFF);
		buffer[nextByteToWrite++] = (byte) ((value >>> 32) & 0xFF);
		buffer[nextByteToWrite++] = (byte) ((value >>> 24) & 0xFF);
		buffer[nextByteToWrite++] = (byte) ((value >>> 16) & 0xFF);
		buffer[nextByteToWrite++] = (byte) ((value >>> 8) & 0xFF);
		buffer[nextByteToWrite++] = (byte) (value & 0xFF);
	}

	public void write_longlong_array(long[] value, int offset, int length) {

		makeRoom(length * 8);

		for (int i = 0; i < length; i++) {
			write_longlong(value[i + offset]);
		}
	}

	public void write_ulong(int value) {

		write_long(value);
	}

	public void write_ulong_array(int[] value, int offset, int length) {

		write_long_array(value, offset, length);
	}

	public void write_ulonglong(long value) {

		write_longlong(value);
	}

	public void write_ulonglong_array(long[] value, int offset, int length) {

		write_longlong_array(value, offset, length);
	}

	public void write_ushort(short value) {

		write_short(value);
	}

	public void write_ushort_array(short[] value, int offset, int length) {

		write_short_array(value, offset, length);
	}

	public void write_any(org.omg.CORBA.Any value) {

		write_TypeCode(value.type());
		value.write_value(this);
	}

	public void write_Object(org.omg.CORBA.Object value) {
		org.omg.CORBA.portable.ObjectImpl objectImpl = (org.omg.CORBA.portable.ObjectImpl) value;

		if (objectImpl == null) {
			// report an error
			System.out.println("CDROutputStream: Not a CORBA Object.");
			return;
		}

		org.huihoo.orbas.orb.ObjRefDelegate delegate = (org.huihoo.orbas.orb.ObjRefDelegate) (objectImpl
				._get_delegate());

		if (delegate == null) {
			// report an error
			System.out
					.println("CDROutputStream: Not a CORBA Object.Can NOT get delegate");
			return;
		}

		org.omg.IOP.IOR ior = delegate.getIor();

		write_boolean(false);
		org.omg.IOP.IORHelper.write(this, ior);
	}

	public void write_TypeCode(org.omg.CORBA.TypeCode value) {
		int tcKind = value.kind().value();
		int memCount = 0;

		try {
			switch (tcKind) {

			case TCKind._tk_null:
			case TCKind._tk_void:
			case TCKind._tk_short:
			case TCKind._tk_long:
			case TCKind._tk_ushort:
			case TCKind._tk_ulong:
			case TCKind._tk_float:
			case TCKind._tk_double:
			case TCKind._tk_boolean:
			case TCKind._tk_char:
			case TCKind._tk_octet:
			case TCKind._tk_any:
			case TCKind._tk_TypeCode:
			case TCKind._tk_longlong:
			case TCKind._tk_ulonglong:
			case TCKind._tk_longdouble:
			case TCKind._tk_wchar:
				write_long(tcKind);
				return;

			case TCKind._tk_objref:
				write_long(tcKind);
				write_string(value.id());
				write_string(value.name());
				return;
			case TCKind._tk_enum:
				write_long(tcKind);
				memCount = value.member_count();
				write_string(value.id());
				write_string(value.name());
				write_long(memCount);

				for (int i = 0; i < memCount; i++) {
					write_string(value.member_name(i));
				}

				return;

			case TCKind._tk_struct:
				write_long(tcKind);
				memCount = value.member_count();
				write_string(value.id());
				write_string(value.name());
				write_long(memCount);

				for (int i = 0; i < memCount; i++) {
					write_string(value.member_name(i));
					write_TypeCode(value.member_type(i));
				}

				return;

			case TCKind._tk_except:
				write_long(tcKind);
				memCount = value.member_count();
				write_string(value.id());
				write_string(value.name());
				write_long(memCount);

				for (int i = 0; i < memCount; i++) {
					write_string(value.member_name(i));
					write_TypeCode(value.member_type(i));
				}

				return;

			case TCKind._tk_union:
				write_long(tcKind);
				write_string(value.id());
				write_string(value.name());
				write_TypeCode(value.discriminator_type());
				write_long(value.default_index());
				memCount = value.member_count();
				write_long(memCount);

				for (int i = 0; i < memCount; i++) {
					// Unknow what to do
					// throw new org.omg.CORBA.NO_IMPLEMENT();

					write_any(value.member_label(i));
					write_string(value.member_name(i));
					write_TypeCode(value.member_type(i));

				}

				return;

			case TCKind._tk_alias:
				write_long(tcKind);
				write_string(value.id());
				write_string(value.name());
				write_TypeCode(value.get_compact_typecode());
				return;

			case TCKind._tk_abstract_interface:
				write_long(tcKind);
				write_string(value.id());
				write_string(value.name());
				return;

			case TCKind._tk_sequence:
				write_long(tcKind);
				write_long(value.length());
				write_TypeCode(value.get_compact_typecode());
				return;
			case TCKind._tk_string:

				write_long(tcKind);
				write_long(0);
				return;
			case TCKind._tk_array:
				write_long(tcKind);
				write_TypeCode(value.get_compact_typecode());
				write_long(value.length());
				return;
			case TCKind._tk_wstring:
				write_long(tcKind);
				write_long(0);
				return;
			case TCKind._tk_fixed:
				throw new org.omg.CORBA.NO_IMPLEMENT();
				// return;
			case TCKind._tk_value:
				throw new org.omg.CORBA.NO_IMPLEMENT();
				// return;
			case TCKind._tk_value_box:
				throw new org.omg.CORBA.NO_IMPLEMENT();
				// return;
			case TCKind._tk_native:
				write_long(tcKind);
				write_string(value.id());
				write_string(value.name());
				return;
			case TCKind._tk_local_interface:
				write_long(tcKind);
				write_string(value.id());
				write_string(value.name());
				return;
			default:
				// report an error
				System.out.println("CDROutputStream Invalid TCKind code: "
						+ tcKind);
				return;

			}
		} catch (org.omg.CORBA.TypeCodePackage.BadKind e1) {
			System.out.println("Bad Kind Exception while writing TypeCode");
			return;
		} catch (org.omg.CORBA.TypeCodePackage.Bounds e2) {
			System.out.println("Bad Kind Exception while writing TypeCode");
			return;
		}

	}

	public void write_Context(org.omg.CORBA.Context ctx,
			org.omg.CORBA.ContextList contexts) {
		return;
	}

	public void write_Principal(org.omg.CORBA.Principal pr) {
		return;
	}

	public void write_fixed(java.math.BigDecimal value) {
		return;
	}

	public void write_fixed(java.math.BigDecimal value, short digits,
			short scale) {
		// throw new org.omg.CORBA.NO_IMPLEMENT();
		return;
	}

	public void write_value(java.io.Serializable value) {
		// Check if null value
		if (value == null) {
			write_long(0);
			return;
		}

		// Check if the value is already marshalled
		java.lang.Integer offset = (java.lang.Integer) valueList.get(value);
		if (offset != null) {
			// already marshlled, then write offset
			write_long(0xffffffff);
			write_long(offset.intValue());
			return;
		}

		valueList.put(value, new Integer(nextByteToWrite));

		String uri = null;
		// custom value
		if (value instanceof org.omg.CORBA.portable.CustomValue) {
			org.omg.CORBA.portable.CustomValue customValue;
			customValue = (org.omg.CORBA.portable.CustomValue) value;
			String[] id = customValue._truncatable_ids();

			int tag = 0;

			uri = getURLCodeBase();

			if (id.length > 1) {
				tag = 0x7fffff00 + MULTIPLE_TYPE_INFORMATION + CHUNK;
			} else {
				tag = 0x7fffff00 + SINGLE_TYPE_INFORMATION + CHUNK;
			}

			if (uri == null) {
				tag = tag + VALUE_NO_CODEBASE;
			} else {
				tag = tag + VALUE_CODEBASE;
			}

			write_long(tag);

			if (uri != null) {
				if (urlIndex != -1) {
					write_ulong(0xffffffff);
					write_ulong(urlIndex);
				} else {
					padding(4);
					urlIndex = nextByteToWrite;
					write_string(uri);
				}
			}

			level++;

			// Put Repository Id

			if (id.length > 1) {
				write_long(id.length);
			}

			for (int i = 0; i < id.length; i++) {
				write_string(id[i]);
			}

			// Custom marshalling
			CDROutputStream output = new CDROutputStream(orb);
			org.huihoo.orbas.orb.OutputStream cdrOut = new org.huihoo.orbas.orb.OutputStream(
					output);
			customValue.marshal(cdrOut);

			write_long(output.getLength());

			byte[] buffer = output.getBuffer();
			write_octet_array(buffer, 0, output.getLength());

			write_long(-level);
			level--;
			return;
		}

		if (value instanceof org.omg.CORBA.portable.StreamableValue) {
			org.omg.CORBA.portable.StreamableValue streamValue;
			streamValue = (org.omg.CORBA.portable.StreamableValue) value;
			String[] id = streamValue._truncatable_ids();

			int tag = 0;

			uri = getURLCodeBase();

			if (id.length > 1) {
				tag = 0x7fffff00 + MULTIPLE_TYPE_INFORMATION + CHUNK;
			} else {
				tag = 0x7fffff00 + SINGLE_TYPE_INFORMATION + NO_CHUNK;
			}

			if (uri == null) {
				tag = tag + VALUE_NO_CODEBASE;
			} else {
				tag = tag + VALUE_CODEBASE;
			}

			write_long(tag);

			if (uri != null) {
				write_string(uri);
			}

			if (id.length > 1) {
				write_long(id.length);
			}

			for (int i = 0; i < id.length; i++) {
				write_string(id[i]);
			}

			if (id.length > 1) {

				level++;
				CDROutputStream output = new CDROutputStream(orb);
				streamValue._write(output);
				write_long(output.getLength());

				byte[] buffer = output.getBuffer();
				write_octet_array(buffer, 0, output.getLength());

				write_long(-level);
				level--;
			} else {
				streamValue._write(this);
			}
		}

		if (!(value instanceof org.omg.CORBA.portable.StreamableValue)) {
			if (value instanceof org.omg.CORBA.portable.ValueBase) {
				String id = ((org.omg.CORBA.portable.ValueBase) value)
						._truncatable_ids()[0];
				write_value(value, id);

				return;
			}

			throw new org.omg.CORBA.BAD_PARAM();
		}
	}

	public void write_value(java.io.Serializable value, java.lang.String rep_id) {
		// Check if null value
		if (value == null) {
			write_long(0);
			return;
		}

		// Check if the value is already marshalled
		java.lang.Integer offset = (java.lang.Integer) valueList.get(value);
		if (offset != null) {
			// already marshlled, then write offset
			write_long(0xffffffff);
			write_long(offset.intValue());
			return;

		}

		valueList.put(value, new Integer(nextByteToWrite));

		if (!(value instanceof org.omg.CORBA.portable.StreamableValue)) {
			// load help class
			int index = 0;
			java.lang.Class clzHelper = null;
			String baseName = rep_id.substring(4, rep_id.lastIndexOf(":"));

			baseName = baseName.replace('/', '.');

			String BoxedValueHelperName = baseName + "Helper";

			while (true) {

				try {
					clzHelper = java.lang.Class.forName(BoxedValueHelperName);
				} catch (ClassNotFoundException ex) {
					System.out.println("Unable to load help class by id of "
							+ rep_id);
					throw new org.omg.CORBA.MARSHAL();
				}

				if (clzHelper != null) {
					break;
				}

				index = BoxedValueHelperName.indexOf(".");

				if (index == -1) {
					break;
				}

				BoxedValueHelperName = BoxedValueHelperName.substring(
						index + 1, BoxedValueHelperName.length());
			}

			if (clzHelper == null) {
				System.out.println("Unable to load help class by id of "
						+ rep_id);
				throw new org.omg.CORBA.MARSHAL();
			}

			org.omg.CORBA.portable.BoxedValueHelper helper = null;
			try {
				helper = (org.omg.CORBA.portable.BoxedValueHelper) clzHelper
						.newInstance();
			} catch (InstantiationException e) {
				System.out.println("Unable to instantiate value help class");
				throw new org.omg.CORBA.MARSHAL();
			} catch (IllegalAccessException e) {
				System.out.println("Unable to instantiate value help class");
				throw new org.omg.CORBA.MARSHAL();
			}

			// wirte fianlly
			write_value(value, helper);
		} else {
			write_value(value);
		}
	}

	public void write_value(java.io.Serializable value, Class clz) {
		write_value(value);
	}

	public void write_value(java.io.Serializable value,
			org.omg.CORBA.portable.BoxedValueHelper factory) {

		// Check if null value
		if (value == null) {
			write_long(0);
			return;
		}

		// Check if the value is already marshalled
		java.lang.Integer offset = (java.lang.Integer) valueList.get(value);
		if (offset != null) {
			// already marshlled, then write offset
			write_long(0xffffffff);
			write_long(offset.intValue());
			return;
		}

		valueList.put(value, new Integer(nextByteToWrite));

		int tag = 0;
		String uri = getURLCodeBase();

		if (uri == null) {
			tag = 0x7fffff00 + VALUE_NO_CODEBASE + SINGLE_TYPE_INFORMATION
					+ NO_CHUNK;
		} else {
			tag = 0x7fffff00 + VALUE_CODEBASE + SINGLE_TYPE_INFORMATION
					+ NO_CHUNK;
		}

		write_long(tag);
		if (uri != null) {
			if (urlIndex != -1) {
				write_ulong(0xffffffff);
				write_ulong(urlIndex);
			} else {
				padding(4);
				urlIndex = nextByteToWrite;
				write_string(uri);
			}
		}

		// Put Repository Id
		write_string(factory.get_id());

		// Then put value
		factory.write_value(this, value);
	}

	public void write_abstract_interface(java.lang.Object object) {
		if (object instanceof org.omg.CORBA.Object) {
			write_boolean(true);
			write_Object((org.omg.CORBA.Object) object);
		} else {
			write_boolean(false);
			write_value((java.io.Serializable) object);
		}
	}

	public void flush() {
	}

	public byte[] getBuffer() {
		return buffer;
	}

	public int getLength() {
		return nextByteToWrite + 1;
	}

	private void padding(int alignSize) {
		int extraBytesUsed = nextByteToWrite % alignSize;

		if (extraBytesUsed != 0) {
			// makeRoom(alignSize);
			nextByteToWrite += alignSize - extraBytesUsed;
		}
	}

	public void makeRoom(int size) {

		if (nextByteToWrite + size > buffer.length - 1) {

			int newLength = buffer.length * 3 / 2 + 1;

			for (;;) {
				if (nextByteToWrite + size > newLength) {
					newLength = newLength * 3 / 2 + 1;
				} else {
					break;
				}
			}

			byte[] newBuffer = new byte[newLength];

			System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
			this.buffer = null;
			this.buffer = newBuffer;
		}

	}

	private String getURLCodeBase() {
		return null;
	}
}
